﻿using System;
using System.Collections.Generic;
using System.Linq;
using Monopoly.Exceptions;

namespace Monopoly.Domain
{
    public class Bank : BoxPropertyOwner
    {
        private const short MAX_HOUSES = 32;
        private const short MAX_HOTELS = 12;
        private const short MAX_PROPERTIES = 28;

        private IList<Hotel> Hotels;
        private IList<House> Houses;
        private IList<BoxProperty> Properties;

        private int FreeParkingAmount;

        /// <summary>
        /// Initialize a new Bank with the right amount of Hotels, Houses and an empty array of properties
        /// </summary>
        public Bank()
        {
            this.FreeParkingAmount = 0;

            this.Hotels = new List<Hotel>(MAX_HOTELS);
            for (int i = 0; i < MAX_HOTELS; i++)
                this.Hotels.Add(new Hotel());

            this.Houses = new List<House>(MAX_HOUSES);
            for (int i = 0; i < MAX_HOUSES; i++)
                this.Houses.Add(new House());

            this.Properties = new List<BoxProperty>();
        }

        /// <summary>
        /// Create a new bank with the inital given list of properties
        /// </summary>
        /// <param name="Properties">List of properties</param>
        public Bank(IList<Box> Properties)
            : this()
        {
            this.AddProperties(Properties);
        }


        /// <summary>
        /// Add a list of properties to the bank collection
        /// </summary>
        /// <param name="Properties">List of properties</param>
        public void AddProperties(IList<Box> Properties)
        {
            foreach (Box Property in Properties)
            {
                ((BoxProperty)Property).SetOwner(this);
                this.Properties.Add((BoxProperty)Property);
            }
        }

        /// <summary>
        /// Add a list of properties to the bank collection
        /// </summary>
        /// <param name="Properties">List of properties</param>
        public void AddProperties(IList<BoxProperty> Properties)
        {
            foreach (BoxProperty Property in Properties)
            {
                ((BoxProperty)Property).SetOwner(this);
                this.Properties.Add(Property);
            }
        }

        public void GiveProperty(BoxProperty BoxProperty, Player Player)
        {
            Player.AddProperty(BoxProperty);
            BoxProperty.SetOwner(Player);
        }

        /// <summary>
        /// Add a hotel to the bank collection
        /// </summary>
        /// <param name="Hotel">A hotel</param>
        public void AddHotel(Hotel Hotel)
        {
            this.Hotels.Add(Hotel);
        }

        /// <summary>
        /// Add a house to the bank collection
        /// </summary>
        /// <param name="House">A house</param>
        public void AddHouse(House House)
        {
            this.Houses.Add(House);
        }

        public void GiveCash(int Amount, Player Player)
        {
            Program.WriteLine("Vous recevez " + Amount.ToEuro(true) + " de la banque");

            Player.AddCash(Amount);
        }

        /// <summary>
        /// Add cash on free parking
        /// </summary>
        /// <param name="CashAmount">The amount of cash</param>
        public void AddCashOnFreeParking(int CashAmount)
        {
            this.FreeParkingAmount += CashAmount;
        }

        public void GiveFreeParkingAmount(Player Player)
        {
            if (this.FreeParkingAmount > 0)
            {
                Program.WriteLine("Vous recevez " + this.FreeParkingAmount.ToEuro(true) + " du Parc Gratuit");

                Player.AddCash(this.RemoveFreeParkingAmount());
            }
            else
                Program.WriteLine("Il n'y a pas d'argent sur le Parc Gratuit");
        }

        /// <summary>
        /// Return free parking amount and set it to 0
        /// </summary>
        /// <returns><code>int</code> Amount in the free parking</returns>
        public int RemoveFreeParkingAmount()
        {
            int FreeParkingAmount = this.FreeParkingAmount;
            this.FreeParkingAmount = 0;
            return FreeParkingAmount;
        }

        /// <summary>
        /// Return 20 000
        /// </summary>
        /// <returns><code>int</code></returns>
        public void GetPassOverGoMoney(Player Player)
        {
            Player.AddCash(20000);
        }

        /// <summary>
        /// Redeem a real estate to a player. Also pay the player
        /// </summary>
        /// <param name="RealEstate">Sold real estate</param>
        /// <param name="Seller">Old owner of the real estate</param>
        /// <param name="Price">Price of the real estate</param>
        public void RedeemRealEstate(RealEstate RealEstate, Player Seller, int Price)
        {
            Seller.AddCash(Price);
            if (RealEstate is House)
                Houses.Add((House)RealEstate);
            else
                Hotels.Add((Hotel)RealEstate);
        }

        /// <summary>
        /// Checks if the bank isn't in an real estate crisis.
        /// </summary>
        /// <param name="Type">Type of real estate</param>
        /// <param name="Quantity">Wanted quantity of the given real estate type</param>
        /// <returns>true if the bank has enough real estate</returns>
        public bool HasEnoughRealEstate(Type Type, int Quantity = 1)
        {
            if (Type == typeof(House))
                return this.Houses.Count >= Quantity;
            else
                return this.Hotels.Count >= Quantity;
        }

        /// <summary>
        /// Allow real estate transaction. Only one item of the given type is added to the field
        /// </summary>
        /// <param name="Type">Type of real estate (House|Hotel)</param>
        /// <param name="Field">Field on which to put the real estate</param>
        public void BuyRealEstate(Type Type, Field Field)
        {
            if (Type == typeof(House))
                if (this.Houses.Count > 0)
                {
                    ((Player)Field.GetOwner()).PayBank(Field.GetHousePrice());
                    Field.AddHouse((House)this.Houses.First());
                    this.Houses.RemoveAt(0);
                }
                else throw new RealEstateCrisisException();
            else
                if (this.Hotels.Count > 0)
                {
                    Field.SaleAllRealEstate(this);
                    Field.AddHotel((Hotel)this.Hotels.First());
                    this.Hotels.RemoveAt(0);
                }
                else throw new RealEstateCrisisException();
        }

        /// <summary>
        /// Allow multiple real estate transaction for houses. Quantity is check before any buy
        /// </summary>
        /// <param name="Type">Type of real estate</param>
        /// <param name="Quantity">Wanted quantity of the given real estate. If Type is Hotel, quantity is ignored</param>
        /// <param name="Field">Field on which to add real estate</param>
        /// <remarks>Uses <code>BuyRealEstate</code> without <code>Quantity</code> parameter</remarks>
        public void BuyRealEstate(Type Type, int Quantity, Field Field)
        {
            IList<RealEstate> Result = new List<RealEstate>();
            if (Type == typeof(House))
                if (this.Houses.Count >= Quantity)
                    for (int i = 0; i < Quantity; i++)
                        BuyRealEstate(typeof(House), Field);
                else
                    throw new RealEstateCrisisException();
            else
                BuyRealEstate(typeof(Hotel), Field);
        }

        /// <summary>
        /// Bank pass through a real estate crisis so a player needs to buy the maximum of houses and equaly populate all same color fields
        /// </summary>
        /// <param name="Field">Original field for hotel selling</param>
        public void BuyMaxHouses(Field Field)
        {
            //Let's start by sale all fields' real estate
            IList<Field> Fields = ((Player)Field.GetOwner()).GetFieldListByColor(Field.GetColor());
            foreach (Field f in Fields)
                f.SaleAllRealEstate(this);

            //Let's buy the maximum number of houses
            int NumberOfFields = Fields.Count;
            while (Field.GetHouseCount() < 4 && this.Houses.Count >= NumberOfFields)
                foreach (Field f in Fields)
                    this.BuyRealEstate(typeof(House), f);
        }

        /// <summary>
        /// Returns the number of available given type real estate
        /// </summary>
        /// <param name="Type">Type of wanted real estate</param>
        /// <returns>Number of remaining buildings</returns>
        public int GetRemainingAvailableRealEstate(Type Type)
        {
            if (typeof(House) == Type)
                return this.Houses.Count;
            else
                return this.Hotels.Count;
        }
    }
}
